// MCKit
// Copyright (c) 2010, Hz Systems LLC
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//	•Redistributions of source code must retain the above copyright notice,
//	 this list of conditions and the following disclaimer.
//	•Redistributions in binary form must reproduce the above copyright notice,
//	 this list of conditions and the following disclaimer in the documentation and/or
//	 other materials provided with the distribution.
//	•Neither the name of Aquatic nor the names of its contributors may be used to 
//	 endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.



#import <objc/runtime.h>
#import "NSData+GZip.h"
#import "NBTTag.h"

@interface NBTData (Private)
-(void)_write:(NSMutableData*)inTarget data:(void*)inData length:(size_t)inLength;
-(void)_writeTagData:(NSMutableData*)inTarget type:(NBTTagType)inType value:(void*)inValue;
-(void)writeByte:(NSMutableData*)inTarget value:(int8_t)inValue;
-(void)writeShort:(NSMutableData*)inTarget value:(int16_t)inValue;
-(void)writeInt:(NSMutableData*)inTarget value:(int32_t)inValue;
-(void)writeLong:(NSMutableData*)inTarget value:(int64_t)inValue;
-(void)writeFloat:(NSMutableData*)inTarget value:(float)inValue;
-(void)writeDouble:(NSMutableData*)inTarget value:(double)inValue;
-(void)writeString:(NSMutableData*)inTarget value:(NSString*)inValue;
-(void)writeByteArray:(NSMutableData*)inTarget value:(NSData*)inValue;
-(void)writeList:(NSMutableData*)inTarget value:(NSArray*)inValue;
-(void)writeCompound:(NSMutableData*)inTarget value:(NSDictionary*)inValue;

-(void*)_read:(size_t)inLength;
-(id)_readTagData:(NBTTagType)inType;
-(int8_t)readByte;
-(int16_t)readShort;
-(int32_t)readInt;
-(int64_t)readLong;
-(float)readFloat;
-(double)readDouble;
-(NSString*)readString;
-(int8_t*)readByteArray:(uint32_t*)len;
-(NSDictionary*)readCompound;
-(NSArray*)readList;
@end

@implementation NBTData
@synthesize data=_data;

-(id)init
{

	self = [super init];
	if(self)
	{
		_data = [[NSMutableData alloc] initWithCapacity:0];
	}
	return self;
}

-(id)initWithFile:(NSString*)filePath
{
	self = [super init];
	if(self)
	{
		_data = [[[NSData dataWithContentsOfFile:filePath] gzipInflate] retain];
		if(!_data)
		{
			[self release];
			return nil;
		}
#if 0
		[_data writeToFile:@"/Volumes/Torque/Development/tests/minecraftFiles/_tests/_inflated.nbt" atomically:YES];
//		PFLogMem(PFLOG_LEVEL_DEBUG_MORE, [inflatedData bytes], [inflatedData length]);
#endif
		_dataSize = [_data length];
		_dataptr = (uint8_t*)[_data bytes];
	}
	return self;
}

-(void)dealloc
{
	if(_dataptr)
		free((void*)_dataptr);
	
	[super dealloc];
}

#pragma mark -

-(void)_write:(NSMutableData*)inTarget data:(void*)inData length:(size_t)inLength
{
	[inTarget appendBytes:inData length:inLength];
}

-(void)_writeTagData:(NSMutableData*)inTarget type:(NBTTagType)inType value:(void*)inValue
{
	switch(inType)
	{
		case kNBTTagType_END:
			break;
			
		case kNBTTagType_BYTE:
			[self writeByte:inTarget value:[(NSNumber*)inValue charValue]];
			break;
			
		case kNBTTagType_SHORT:
			[self writeShort:inTarget value:[(NSNumber*)inValue shortValue]];
			break;
			
		case kNBTTagType_INT:
			[self writeInt:inTarget value:[(NSNumber*)inValue longValue]];
			break;
			
		case kNBTTagType_LONG:
			[self writeLong:inTarget value:[(NSNumber*)inValue longLongValue]];
			break;
			
		case kNBTTagType_FLOAT:
			[self writeFloat:inTarget value:[(NSNumber*)inValue floatValue]];
			break;
			
		case kNBTTagType_DOUBLE:
			[self writeDouble:inTarget value:[(NSNumber*)inValue doubleValue]];
			break;
			
		case kNBTTagType_BYTE_ARRAY:
			[self writeByteArray:inTarget value:inValue ];
			break;
			
			
		case kNBTTagType_STRING:
			[self writeString:inTarget value:inValue];
			break;
			
		case kNBTTagType_LIST:
			[self writeList:inTarget value:inValue];
			break;
			
			
		case kNBTTagType_COMPOUND:
			[self writeCompound:inTarget value:inValue];
			break;
	}
}

-(void)writeTag:(NSMutableData*)inTarget tag:(NBTTag*)inValue
{
	NBTTagType tagType = inValue.type;
	
	[self writeByte:inTarget value:tagType];
	
	if (tagType != kNBTTagType_END)
	{
		[self writeString:inTarget value:inValue.name];
		[self _writeTagData:inTarget type:tagType value:inValue.value];
	}
}

-(void)writeByte:(NSMutableData*)inTarget value:(int8_t)inValue
{
	[self _write:inTarget data:&inValue length:sizeof(char)];
}

-(void)writeShort:(NSMutableData*)inTarget value:(int16_t)inValue
{
	int16_t swapedValue = CFSwapInt16HostToBig(inValue);
	[self _write:inTarget data:&swapedValue length:sizeof(int16_t)];
}

-(void)writeInt:(NSMutableData*)inTarget value:(int32_t)inValue
{
	int32_t swapedValue = CFSwapInt32HostToBig(inValue);
	[self _write:inTarget data:&swapedValue length:sizeof(int32_t)];
}

-(void)writeLong:(NSMutableData*)inTarget value:(int64_t)inValue
{
	int64_t swapedValue = CFSwapInt64HostToBig(inValue);
	[self _write:inTarget data:&swapedValue length:sizeof(int64_t)];
}

-(void)writeFloat:(NSMutableData*)inTarget value:(float)inValue
{
	union DoubleBytes
	{
		float DoubleValue;
		unsigned char Byte[sizeof(float)];
	} result;
	result.DoubleValue = inValue;
	for(int i=sizeof(float)-1; i>=0; i--)
		[self _write:inTarget data:&result.Byte[i] length:1];
}

-(void)writeDouble:(NSMutableData*)inTarget value:(double)inValue
{
	union DoubleBytes
	{
		double DoubleValue;
		unsigned char Byte[sizeof(double)];
	} result;
	result.DoubleValue = inValue;
	for(int i=sizeof(double)-1; i>=0; i--)
		[self _write:inTarget data:&result.Byte[i] length:1];
}

-(void)writeString:(NSMutableData*)inTarget value:(NSString*)inValue
{
	int16_t len = [inValue length];
	[self writeShort:inTarget value:len];
	[self _write:inTarget data:(void*)[inValue UTF8String] length:len];
}

-(void)writeByteArray:(NSMutableData*)inTarget value:(NSData*)inValue
{
	[self writeInt:inTarget value:[inValue length]];
	[self _write:inTarget data:((void*)[inValue bytes]) length:[inValue length]];
}

-(void)writeList:(NSMutableData*)inTarget value:(NSArray*)inValue
{
	NBTTagType tagType = kNBTTagType_BYTE;
	
	if([inValue count] > 0)
		tagType = ((NBTTag*)[inValue objectAtIndex:0]).type;
	
	[self writeByte:inTarget value:tagType];
	[self writeInt:inTarget value:[inValue count]];

	for(NBTTag *value in inValue)
		[self _writeTagData:inTarget type:tagType value:value.value];
}

-(void)writeCompound:(NSMutableData*)inTarget value:(NSDictionary*)inValue
{
	for(id key in inValue)
	{
		NBTTag *obj = [inValue objectForKey:key];
		[self writeTag:inTarget tag:obj];
	}
	[self writeByte:inTarget value:kNBTTagType_END];
}

#pragma mark -

-(void*)_read:(size_t)inLength
{
	void *data = (void*)([_data bytes] + _cursor);
	_cursor += inLength;
	return data;
}

-(id)_readTagData:(NBTTagType)inType
{
	id result = NULL;
	switch(inType)
	{
		case kNBTTagType_END:
			result = [[NBTEndTag alloc] init];
			break;
			
		case kNBTTagType_BYTE:
			result = [[NBTByteTag alloc] initWithByte:[self readByte]];
			break;
			
		case kNBTTagType_SHORT:
			result = [[NBTShortTag alloc] initWithShort:[self readShort]];
			break;
			
		case kNBTTagType_INT:
			result = [[NBTIntTag alloc] initWithInt:[self readInt]];
			break;
			
		case kNBTTagType_LONG:
			result = [[NBTLongTag alloc] initWithLong:[self readLong]];
			break;
			
		case kNBTTagType_FLOAT:
			result = [[NBTFloatTag alloc] initWithFloat:[self readFloat]];
			break;
			
		case kNBTTagType_DOUBLE:
			result = [[NBTDoubleTag alloc] initWithDouble:[self readDouble]];
			break;
			
		case kNBTTagType_BYTE_ARRAY:
		{
			uint32_t len = 0;
			int8_t *data = [self readByteArray:&len];
			result = [[NBTByteArrayTag alloc] initWithBytes:data length:len];
		}
			break;
			
			
		case kNBTTagType_STRING:
			result = [[NBTStringTag alloc] initWithString:[self readString]];
			break;
			
		case kNBTTagType_LIST:
			result = [[NBTListTag alloc] initWithTags:[self readList]];
			break;
			
			
		case kNBTTagType_COMPOUND:
			result = [[NBTCompoundTag alloc] initWithTags:[self readCompound]];
			break;
	}
	
	return result;
}

-(id)readTag
{
	NSString *name = NULL;
	int8_t	type = 0;
	NBTTag *tag = nil;
	
	type = [self readByte]; 
	if(type != kNBTTagType_END)
		name = [self readString];
	
	tag = [self _readTagData:type];
	if(name)
		tag.name = name;
	return tag;
}

-(int8_t)readByte
{
	int8_t *value = [self _read:sizeof(char)];
	return *value;
}

-(int16_t)readShort
{
	int16_t shortVal = OSReadBigInt16([self _read:sizeof(int16_t)], 0);
	return shortVal;
}

-(int32_t)readInt
{
	int32_t intVal = OSReadBigInt32([self _read:sizeof(int32_t)], 0);
	return intVal;
}

-(int64_t)readLong
{
	int64_t longVal = OSReadBigInt64([self _read:sizeof(int64_t)], 0);
	return longVal;
}

-(float)readFloat
{
	CFSwappedFloat32 result;
    memcpy(&result, [self _read:sizeof(CFSwappedFloat32)], sizeof(CFSwappedFloat32));
    return CFConvertFloatSwappedToHost(result);
}

-(double)readDouble
{
	CFSwappedFloat64 result;
	memcpy(&result, [self _read:sizeof(CFSwappedFloat64)], sizeof(CFSwappedFloat64));
    return CFConvertDoubleSwappedToHost(result);
}

-(NSString*)readString
{
	int16_t len = [self readShort];
	void *bytes = [self _read:len];
	
	return [[[NSString alloc] initWithBytes:bytes length:len encoding:NSUTF8StringEncoding] autorelease];
}

-(int8_t*)readByteArray:(uint32_t*)len
{
	*len = [self readInt];
	return (int8_t*)[self _read:*len];
}

-(NSDictionary*)readCompound
{
	NSMutableDictionary *tags = [[NSMutableDictionary alloc] initWithCapacity:0];
	
	while(true)
	{
		NBTTag *tag = [self readTag];
		if(!tag || tag.type == kNBTTagType_END)
			break;
		
		[tags setObject:tag forKey:tag.name];
	}
	return [tags autorelease];
}

-(NSArray*)readList
{
	NSMutableArray *outArray = NULL;
	int8_t tagType = [self readByte];
	int32_t count = [self readInt];
	
	outArray = [[NSMutableArray alloc] initWithCapacity:0];

	for (int32_t i = 0; i < count; ++i)
		[outArray addObject:[self _readTagData:tagType]];

	return [outArray autorelease];
}

@end


#pragma mark -
#pragma mark Tag Handlers


@implementation NBTTag
@synthesize type=_type, name=_name, value=_value;

-(id)initWithName:(NSString*)inName
{
	self = [super init];
	if(self)
	{
		self.name = inName;
	}
	return self;
}

-(NSString*)description
{
	return [NSString stringWithFormat:@"[%s('%@')] value: %@", class_getName([self class]), self.name, self.value];
}

-(void)writeToFile:(NSString*)path
{
	NSMutableData *data = [[NSMutableData alloc] initWithCapacity:0];
	NBTData *nuNBTData = [[NBTData alloc] init];
	[nuNBTData writeTag:data tag:self];
	[[data gzipDeflate] writeToFile:path atomically:YES];
	[nuNBTData release];	
}

@end


#pragma mark -


@implementation NBTByteTag

-(id)initWithByte:(int8_t)inValue
{
	self = [super init];
	if(self)
	{
		self.value = [NSNumber numberWithChar:inValue];
	}
	
	return self;
}

-(id)initWithName:(NSString *)inName byteValue:(int8_t)inValue
{
	self = [super initWithName:inName];
	if(self)
	{
		self.value = [NSNumber numberWithChar:inValue];
	}

	return self;
}

-(NBTTagType)type
{
	return kNBTTagType_BYTE;
}


@end


#pragma mark -


@implementation NBTShortTag

-(id)initWithShort:(int16_t)inValue
{
	self = [super init];
	if(self)
	{
		self.value = [NSNumber numberWithShort:inValue];
	}
	
	return self;
}

-(id)initWithName:(NSString *)inName shortValue:(int16_t)inValue
{
	self = [super initWithName:inName];
	if(self)
	{
		self.value = [NSNumber numberWithShort:inValue];
	}
	return self;
}

-(NBTTagType)type
{
	return kNBTTagType_SHORT;
}

@end


#pragma mark -


@implementation NBTIntTag

-(id)initWithInt:(int32_t)inValue
{
	self = [super init];
	if(self)
	{
		self.value = [NSNumber numberWithInt:inValue];
	}
	
	return self;
}

-(id)initWithName:(NSString *)inName intValue:(int32_t)inValue
{
	self = [super initWithName:inName];
	if(self)
	{
		self.value = [NSNumber numberWithInt:inValue];
	}
	return self;
}

-(NBTTagType)type
{
	return kNBTTagType_INT;
}

@end


#pragma mark -


@implementation NBTLongTag

-(id)initWithLong:(int64_t)inValue
{
	self = [super init];
	if(self)
	{
		self.value = [NSNumber numberWithLongLong:inValue];
	}
	
	return self;
}

-(id)initWithName:(NSString *)inName longValue:(int64_t)inValue
{
	self = [super initWithName:inName];
	if(self)
	{
		self.value = [NSNumber numberWithLongLong:inValue];
	}
	return self;
}

-(NBTTagType)type
{
	return kNBTTagType_LONG;
}

@end


#pragma mark -


@implementation NBTFloatTag

-(id)initWithFloat:(float)inValue
{
	self = [super init];
	if(self)
	{
		self.value = [NSNumber numberWithFloat:inValue];
	}
	
	return self;
}

-(id)initWithName:(NSString *)inName floatValue:(float)inValue
{
	self = [super initWithName:inName];
	if(self)
	{
		self.value = [NSNumber numberWithFloat:inValue];
	}
	return self;
}

-(NBTTagType)type
{
	return kNBTTagType_FLOAT;
}

@end


#pragma mark -


@implementation NBTDoubleTag

-(id)initWithDouble:(double)inValue
{
	self = [super init];
	if(self)
	{
		self.value = [NSNumber numberWithDouble:inValue];
	}
	
	return self;
}

-(id)initWithName:(NSString *)inName doubleValue:(double)inValue
{
	self = [super initWithName:inName];
	if(self)
	{
		self.value = [NSNumber numberWithDouble:inValue];
	}
	return self;
}

-(NBTTagType)type
{
	return kNBTTagType_DOUBLE;
}

@end


#pragma mark -


@implementation NBTByteArrayTag

-(id)initWithBytes:(void*)inBytes length:(uint32_t)inLength
{
	self = [super init];
	if(self)
	{
		self.value = [NSData dataWithBytes:inBytes length:inLength];
	}
	
	return self;
}

-(id)initWithName:(NSString *)inName bytes:(void*)inBytes length:(uint32_t)inLength
{
	self = [super initWithName:inName];
	if(self)
	{
		self.type = kNBTTagType_BYTE_ARRAY;
		self.value = [NSData dataWithBytes:inBytes length:inLength];
	}
	return self;
}

-(NSString*)description
{
	return [NSString stringWithFormat:@"<NBTByteArrayTag %p>",self];
}

-(NBTTagType)type
{
	return kNBTTagType_BYTE_ARRAY;
}

@end


#pragma mark -


@implementation NBTStringTag

-(id)initWithString:(NSString*)inString
{
	self = [super init];
	if(self)
	{
		self.value = inString;
	}
	
	return self;
}

-(id)initWithName:(NSString *)inName string:(NSString*)inString
{
	self = [super initWithName:inName];
	if(self)
	{
		self.type = kNBTTagType_STRING;
		self.value = inString;
	}
	return self;
}

-(NBTTagType)type
{
	return kNBTTagType_STRING;
}

@end


#pragma mark -


@implementation NBTListTag
@synthesize tagType;

-(id)initWithTags:(NSArray*)inTags
{
	self = [super init];
	if(self)
	{
		self.value = inTags;
	}
	
	return self;
}

-(id)initWithName:(NSString *)inName className:(NSString*)inClassName list:(NSArray*)inList
{
	self = [super initWithName:inName];
	if(self)
	{
		self.tagType = inClassName;
		self.value = inList;
	}
	return self;
}

-(id)initWithName:(NSString *)inName class:(Class)inClassName list:(NSArray*)inList
{
	self = [super initWithName:inName];
	if(self)
	{
		self.tagType = [NSString stringWithUTF8String:class_getName(inClassName)];
		self.value = inList;
	}
	return self;
}

-(NSString*)description
{
	return [NSString stringWithFormat:@"<NBTListTag[%p] type:%@>",self, self.tagType];
}

 -(NBTTagType)type
{
	return kNBTTagType_LIST;
}

@end


#pragma mark -


@implementation NBTCompoundTag

-(id)initWithTags:(NSDictionary*)inTags
{
	self = [super init];
	if(self)
	{
		self.value = [inTags mutableCopy];
	}
	
	return self;
}

-(id)initWithName:(NSString *)inName map:(NSDictionary*)inMap
{
	self = [super initWithName:inName];
	if(self)
	{
		_value = [[NSMutableDictionary alloc] initWithCapacity:0];
		if(inMap)
			[(NSMutableDictionary*)_value addEntriesFromDictionary:inMap];
	}
	return self;
}

-(NBTTagType)type
{
	return kNBTTagType_COMPOUND;
}

-(void)addChild:(NBTTag*)inTag
{
	[(NSMutableDictionary*)_value setObject:inTag forKey:inTag.name];
}

-(id)tagWithName:(NSString*)inKey
{
	return [(NSMutableDictionary*)_value objectForKey:inKey];
}

@end


#pragma mark -


@implementation NBTEndTag

-(NBTTagType)type
{
	return kNBTTagType_END;
}

@end
